/**
 * This file is part of Logisim-evolution.
 *
 * Logisim-evolution is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * Logisim-evolution is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with Logisim-evolution.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Original code by Carl Burch (http://www.cburch.com), 2011.
 * Subsequent modifications by:
 *   + Haute École Spécialisée Bernoise
 *     http://www.bfh.ch
 *   + Haute École du paysage, d'ingénierie et d'architecture de Genève
 *     http://hepia.hesge.ch/
 *   + Haute École d'Ingénierie et de Gestion du Canton de Vaud
 *     http://www.heig-vd.ch/
 *   + REDS Institute - HEIG-VD, Yverdon-les-Bains, Switzerland
 *     http://reds.heig-vd.ch
 * This version of the project is currently maintained by:
 *   + Kevin Walsh (kwalsh@holycross.edu, http://mathcs.holycross.edu/~kwalsh)
 */
package com.cburch.logisim.std.hdl;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Scanner;

import javax.swing.JOptionPane;

import com.cburch.logisim.std.hdl.VhdlSimulator.State;
import com.cburch.logisim.tools.MessageBox;
import com.cburch.logisim.util.FileUtil;
import com.cburch.logisim.util.Softwares;

/**
 * The TCL binder is a TCL program creating a socket server. The signals have to
 * be written to the binder who will drive the simulation and send back the
 * output signals.
 * <p/>
 * The binder is started when the VHDL simulation is enabled. Once started, it
 * writes a ready flag to his stdout that is catched by Logisim to know the
 * binder is ready. This way we ensure the socket is started before trying to
 * connect.
 * <p/>
 * To end the binder, we send an "end" flag through the socket and wait for it
 * to finish. This causes Logisim to hang if the binder doesn't listen to the
 * socker. That can happen when unexpected behavior of the simulation occurs.
 *
 * @author christian.mueller@heig-vd.ch
 */
class VhdlSimulatorTclBinder {

  private ProcessBuilder builder;
  private Process process;
  private Boolean running = false;

  private VhdlSimulator vhdlSimulator;

  public VhdlSimulatorTclBinder(VhdlSimulator vs) {

    vhdlSimulator = vs;

    List<String> command = new ArrayList<String>();

    command.add(FileUtil.correctPath(Softwares.getQuestaPath())
        + Softwares.QUESTA_BIN[Softwares.VSIM]);

    command.add("-c");
    command.add("-do");
    command.add("do ../run.tcl " + vs.getSocketClient().getServerPort());
    command.add("-errorfile");
    command.add("../questasim_errors.log");

    builder = new ProcessBuilder(command);

    Map<String, String> env = builder.environment();
    env.put("LM_LICENSE_FILE", "1650@eilic01");

    builder.directory(new File(VhdlSimulator.SIM_PATH + "comp/"));

    /* Redirect error on stdout */
    builder.redirectErrorStream(true);
  }

  public Boolean isRunning() {
    return running;
  }

  public void start() {

    try {
      process = builder.start();
    } catch (IOException e) {
      e.printStackTrace();
      System.err.printf("Cannot run TCL binder to Questasim : %s\n", e.getMessage());
      running = false;
      return;
    }

    /* This thread checks the binder started well, it's run from now */
    new Thread(new Runnable() {

      @Override
      public void run() {
        /* Through this we can get the process output */
        BufferedReader reader = new BufferedReader(
            new InputStreamReader(process.getInputStream()));
        String line;
        try {
          String errorMessage = "You may disable VHDL simulation in the simulation menu if this occurs again\n\n";

          /* Here we check that the binder has correctly started */
          while ((line = reader.readLine()) != null) {

            vhdlSimulator.getProject().getFrame()
                .getVhdlSimulatorConsole().append(line + "\n");

            errorMessage += "\n" + line;
            if (line.contains("TCL_BINDER_RUNNING")) {
              running = true;

              new Thread(new Runnable() {
                public void run() {
                  Scanner sc = new Scanner(
                      new InputStreamReader(
                          process.getInputStream()));
                  String nextLine;
                  while (sc.hasNextLine()) {
                    nextLine = sc.nextLine();
                    if (nextLine.length() > 0)
                      vhdlSimulator.getProject()
                          .getFrame()
                          .getVhdlSimulatorConsole()
                          .append(nextLine + "\n");
                  }
                  sc.close();
                }
              }).start();

              vhdlSimulator.tclStartCallback();
              return;
            }
          }

          MessageBox userInfoBox = new MessageBox(
              "Error starting VHDL simulator", errorMessage,
              JOptionPane.ERROR_MESSAGE);
          userInfoBox.show();
          vhdlSimulator.setState(State.ENABLED);

        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }).start();
  }

  public void stop() {
    if (!running)
      return;

    /* We ask the binder to end itself */
    vhdlSimulator.getSocketClient().send("end");

    /* Wait for the process to end */
    /*
     * FIXME: this can be a bad idea, it will crash logisim if the binder
     * doesn't end
     */
    // try {
    // process.waitFor();
    // } catch (InterruptedException e) {
    // e.printStackTrace();
    // System.err.println(e.getMessage());
    // }

    process.destroy();
    running = false;
  }
}
